/*
 * Copyright (C) 1994-2021 Altair Engineering, Inc.
 * For more information, contact Altair at www.altair.com.
 *
 * This file is part of both the OpenPBS software ("OpenPBS")
 * and the PBS Professional ("PBS Pro") software.
 *
 * Open Source License Information:
 *
 * OpenPBS is free software. You can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * OpenPBS is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Commercial License Information:
 *
 * PBS Pro is commercially licensed software that shares a common core with
 * the OpenPBS software.  For a copy of the commercial license terms and
 * conditions, go to: (http://www.pbspro.com/agreement.html) or contact the
 * Altair Legal Department.
 *
 * Altair's dual-license business model allows companies, individuals, and
 * organizations to create proprietary derivative works of OpenPBS and
 * distribute them - whether embedded or bundled with other software -
 * under a commercial license agreement.
 *
 * Use of Altair's trademarks, including but not limited to "PBS™",
 * "OpenPBS®", "PBS Professional®", and "PBS Pro™" and Altair's logos is
 * subject to Altair's trademark licensing policies.
 */

/**
 * @file	pbs_stathost.c
 * @brief
 * Return the combined status of the vnodes on a host or all hosts.
 */

#include <pbs_config.h> /* the master config generated by configure */

#include "libpbs.h"
#include "attribute.h"

/* data structure and local data items private to this file */

/* This structure is used to determined the set of separate "hosts"   */
struct host_list {
	char hl_name[PBS_MAXHOSTNAME + 1]; /* host value */
	struct batch_status *hl_node;	   /* count of vnodes in host */
};

/* This next structure is used to track and sum consumable resources */

struct consumable {
	char *cons_resource;
	char *cons_avail_str;	  /* value in r_available if not consummable  */
	long long cons_avail_sum; /* sum of values in r_available if consumm. */
	long long cons_assn_sum;  /* sum of values in r_assigned  if consumm. */
	short cons_k;		  /* set if "size" type (kb) */
	short cons_consum;	  /* set if resource is consumable */
	short cons_set;		  /* set if resource has a value */
};

/*
 * get_resource_value - for the named resource in the indicated attribute,
 *	resources_assigned or resources_available, return the value of the
 *	resource (as a string).   NULL is returned if the resources isn't there
 */

static char *
get_resource_value(char *attrn, char *rname, struct attrl *pal)
{
	while (pal) {
		if ((strcasecmp(pal->name, attrn) == 0) &&
		    (strcasecmp(pal->resource, rname) == 0))
			return pal->value;
		pal = pal->next;
	}
	return NULL;
}

/**
 * @brief
 * add_consumable_entry - add an entry for a resource into the consumable array
 *
 * @par If the resource is found in resources_assigned, it is considered
 *	to be "consummable" and the various values are added together;
 *	- the resource is "consumable" and it is so flagged.
 *	If the resource is already in the table the consumable flag is updated
 *
 * @par If the resource is not consumable, the value string from the attrib
 *	is used acording to the following rules:
 *	 - If cons_avail_str is null, use the attrib value
 *         else if cons_avail_str == attrib value, no change
 *         else repace cons_avail_str with "<various>"
 *
 * @param[in]	patl	Pointer to element in attribute list
 * @param[in]	consum_flag	Whether the resource is consumable
 * @param[in]	consum	The array of consumables
 * @param[in]	consum	Number of elements in consum array
 *
 * @return	void
 */
static void
add_consumable_entry(struct attrl *patl,
		     int consum_flag,
		     struct consumable **consum,
		     int *consumable_size)
{
	int i;
	struct consumable *newc;

	if (!patl || !consum || !consumable_size)
		return;

	/* Ignore indirect resources (those that contain '@') */
	if (strchr(patl->value, '@') != NULL)
		return;

	for (i = 0; i < *consumable_size; ++i) {
		if (((*consum) + i)->cons_resource == NULL)
			continue;
		if (strcasecmp(patl->resource, ((*consum) + i)->cons_resource) == 0) {
			((*consum) + i)->cons_consum |= consum_flag;
			break;
		}
	}
	if (i == *consumable_size) {
		/* need to add this resource */
		newc = realloc(*consum, sizeof(struct consumable) * (*consumable_size + 1));
		if (newc) {
			*consum = newc;
			(*consumable_size)++;
			if ((((*consum) + i)->cons_resource = strdup(patl->resource)) == NULL) {
				free(newc);
				(*consumable_size)--;
				pbs_errno = PBSE_SYSTEM;
				return;
			}
			((*consum) + i)->cons_avail_str = NULL;
			((*consum) + i)->cons_avail_sum = 0;
			((*consum) + i)->cons_assn_sum = 0;
			((*consum) + i)->cons_k = 0;
			((*consum) + i)->cons_consum = consum_flag;
			((*consum) + i)->cons_set = 0;
		} else {
			pbs_errno = PBSE_SYSTEM;
			return; /* realloc failed, just return */
		}
	}
	/* Set cons_k for size values */
	if (strpbrk(patl->value, "kKmMgGtTpPbBwW") != NULL)
		((*consum) + i)->cons_k = 1;
}

/**
 * @brief
 * 	build_host_list - performs two functions while running through the vnodes
 *	1. builds a list of the various host names found in
 *	   resource_available.host
 *	2. determines which resources are in resources_assigned to know
 *	   which are consumable (and should be summed together).
 */

static void
build_host_list(struct batch_status *pbst,
		struct host_list **phost_list,
		int *host_list_size,
		struct consumable **consum,
		int *consumable_size)
{
	int i;
	char *hostn;
	struct host_list *newhl;
	struct attrl *patl;

	/* clear existing entries for reuse */

	for (i = 0; i < *host_list_size; ++i) {
		((*phost_list) + i)->hl_name[0] = '\0';
		((*phost_list) + i)->hl_node = NULL;
	}

	while (pbst) {

		/* if need be, add host_list entry for this host */
		hostn = get_resource_value(ATTR_rescavail, "host", pbst->attribs);
		if (hostn) {
			for (i = 0; i < *host_list_size; ++i) {
				if (strcasecmp(hostn, ((*phost_list) + i)->hl_name) == 0)
					break;
			}
			if (i == *host_list_size) {
				/* need to add slot for this host */
				newhl = (struct host_list *) realloc((*phost_list),
								     sizeof(struct host_list) * (*host_list_size + 1));
				if (newhl) {
					*phost_list = newhl;
					(*host_list_size)++;
				} else {
					pbs_errno = PBSE_SYSTEM;
					return; /* memory allocation failure */
				}
				strcpy(((*phost_list) + i)->hl_name, hostn);
				((*phost_list) + i)->hl_node = pbst;

			} else {
				((*phost_list) + i)->hl_node = NULL; /* null for multiple */
			}
		}

		/* now look to see what resources are in "resources_assigned" */

		patl = pbst->attribs;
		while (patl) {
			if (strcmp(patl->name, ATTR_rescavail) == 0)
				add_consumable_entry(patl, 0, consum, consumable_size);
			else if (strcmp(patl->name, ATTR_rescassn) == 0)
				add_consumable_entry(patl, 1, consum, consumable_size);
			patl = patl->next;
		}

		pbst = pbst->next;
	}

	return;
}

/**
 * @brief
 * sum_a_resources - add the value of the specified consumable resource
 *	into the "consumable" structure entry for that resource.
 *	"sized" valued resources are adjusted to be in "kb".
 *
 * @param[in]	psum	Pointer to resource
 * @param[in]	avail	Available or assigned list flag
 * @param[in]	value	Value of resource as a string
 * @param[in]	various	Label to use for multiple values
 *
 * @return	void
 */

static void
sum_a_resource(struct consumable *psum, int avail, char *value,
	       char *various)
{
	long long amt;
	char *pc;

	if (!psum || !value || !various)
		return;

	if (psum->cons_consum == 0) {
		/* not a consumable resource */
		if (avail == 0)
			return; /* this shouldn't happen, but no sweat */
		if (psum->cons_avail_str == NULL) {
			if ((psum->cons_avail_str = strdup(value)) == NULL) {
				pbs_errno = PBSE_SYSTEM;
				return;
			}
		} else if (strcasecmp(psum->cons_avail_str, value) != 0) {
			if (psum->cons_avail_str)
				free(psum->cons_avail_str);
			if ((psum->cons_avail_str = strdup(various)) == NULL) {
				pbs_errno = PBSE_SYSTEM;
				return;
			}
		}
		psum->cons_set = 1;
		return;
	}

	/* Ignore indirect resources (those that contain '@') */
	if (strchr(value, '@') != NULL)
		return;

	/* the resource is consumable so we need to add it to the current val */

	amt = strtol(value, &pc, 10);

	if (strpbrk(pc, "kKmMgGtTpPbBwW") != NULL) {

		/* adjust value to Kilobytes */
		if ((*pc == 'm') || (*pc == 'M'))
			amt = amt << 10;
		else if ((*pc == 'g') || (*pc == 'G'))
			amt = amt << 20;
		else if ((*pc == 't') || (*pc == 'T'))
			amt = amt << 30;
		else if ((*pc == 'p') || (*pc == 'P'))
			amt = amt << 30;
		else if ((*pc != 'k') && (*pc != 'K'))
			amt = amt >> 10;

		/* does the current sum need to be adjusted */
		if (psum->cons_k == 0) {
			psum->cons_avail_sum = psum->cons_avail_sum << 10;
			psum->cons_assn_sum = psum->cons_assn_sum << 10;
			psum->cons_k = 1;
		}
	}

	if (avail)
		psum->cons_avail_sum += amt;
	else
		psum->cons_assn_sum += amt;

	psum->cons_set = 1;
}

/**
 * @brief
 * 	sum_resources - for each resource found in the collection of vnodes with
 *	the have "host", the resoruces in resources_available and
 *	resources_assigned are summed.
 */
static void
sum_resources(struct batch_status *pbs,
	      struct batch_status *working,
	      const char *hostn,
	      struct consumable *consum,
	      int consumable_size,
	      char *various)
{
	char *curhn;
	int i;
	char *val;

	/* clear sums */
	for (i = 0; i < consumable_size; ++i) {
		if ((consum + i)->cons_avail_str) {
			free((consum + i)->cons_avail_str);
			(consum + i)->cons_avail_str = NULL;
		}
		(consum + i)->cons_avail_sum = 0;
		(consum + i)->cons_assn_sum = 0;
	}

	while (pbs) {

		curhn = get_resource_value(ATTR_rescavail, "host", pbs->attribs);
		if (curhn && strcasecmp(curhn, hostn) == 0) {
			for (i = 0; i < consumable_size; ++i) {

				val = get_resource_value(ATTR_rescavail, (consum + i)->cons_resource, pbs->attribs);
				sum_a_resource(consum + i, 1, val, various);

				val = get_resource_value(ATTR_rescassn, (consum + i)->cons_resource, pbs->attribs);
				sum_a_resource(consum + i, 0, val, various);
			}
		}

		pbs = pbs->next;
	}
}

/*
 * attr_names array - this array contains the possible names of vnode/host
 *	attributes to create for a "host" batch_status reply
 *	The values to be returned in the attrl list for the host are constructed
 *	in the an_value element and then converted into the attrls by the
 *	build_collective() function.
 */

#ifdef NAS /* localmod 012 */
#define SKIP_FIRST 1
#define SKIP_REST 2
#define CATENATE 4
#define UNIQUE 8
#define SKIP_ALL (SKIP_FIRST | SKIP_REST)

static struct attr_names {
	char *an_name;
	int an_type;
	int an_rest;
	char *an_value;
} attr_names[] = {
	{ATTR_NODE_Mom, UNIQUE, 0, NULL},
	{ATTR_NODE_Port, 0, 0, NULL},
	{ATTR_version, 0, 0, NULL},
	{ATTR_NODE_ntype, 0, 0, NULL},
	{ATTR_NODE_state, UNIQUE, 0, NULL},
	{ATTR_NODE_pcpus, SKIP_REST, 0, NULL},
	{ATTR_p, 0, 0, NULL},
	{ATTR_NODE_jobs, CATENATE | SKIP_FIRST, 0, NULL},
	{ATTR_maxrun, 0, 0, NULL},
	{ATTR_maxuserrun, 0, 0, NULL},
	{ATTR_maxgrprun, 0, 0, NULL},
	{ATTR_NODE_No_Tasks, SKIP_REST, 0, NULL},
	{ATTR_PNames, 0, 0, NULL},
	{ATTR_NODE_resvs, UNIQUE, 0, NULL},
	{ATTR_queue, UNIQUE, 0, NULL},
	{ATTR_comment, UNIQUE, 0, NULL},
	{ATTR_NODE_resv_enable, 0, 0, NULL},
	{ATTR_NODE_NoMultiNode, 0, 0, NULL},
	{ATTR_NODE_Sharing, UNIQUE, 0, NULL},
	{ATTR_NODE_ProvisionEnable, 0, 0, NULL},
	{ATTR_NODE_current_aoe, 0, 0, NULL},
	{ATTR_NODE_License, 0, 0, NULL},
	{ATTR_NODE_LicenseInfo, 0, 0, NULL},
	{ATTR_NODE_TopologyInfo, 0, 0, NULL},
	{ATTR_NODE_VnodePool, 0, 0, NULL},
	{ATTR_NODE_power_provisioning, 0, 0, NULL},
	{ATTR_NODE_current_eoe, 0, 0, NULL},
	{ATTR_NODE_poweroff_eligible, 0, 0, NULL},
	{ATTR_NODE_last_state_change_time, 0, 0, NULL},
	{ATTR_NODE_last_used_time, 0, 0, NULL},
	{ATTR_rescavail, SKIP_ALL, 0, NULL},
	{ATTR_rescassn, SKIP_ALL, 0, NULL},
	{NULL, 0, 0, NULL}};

/**
 * @brief
 * 	build_collective - for each vnode in the original batch_status, pbs,
 *	apply the following rules to build "host" attributes in newbsr entry
 *	"the array" refers to the array attr_names[].an_value --
 *	1. if "resources_assigned" or "resources_available", skip for now
 *	2. else if that attribute in the array has a null value, dup the value
 *		2.5. but record the null if UNIQUE form of CATENATE
 *	3. else if CATENATE attribute, append string to that in the array
 *		3.5. possibly suppress duplicates
 *	4. else if pbs attribute and array value are different, set the array
 *		to "<various>" if not already so set
 *	5. Then add in the resources_available/assigned from the consum struct
 */

static void
build_collective(struct batch_status *pbs,
		 struct batch_status *newbsr,
		 char *hostn,
		 struct consumable *consum,
		 int consumable_size,
		 char *various)
{
	char convtbuf[256];
	struct attrl *cupatl;
	struct attrl *hdpatl;
	struct attrl *nwpatl;
	char *curhn;
	char *dup;
	char *sp;
	int i;
	size_t len;

	for (i = 0; attr_names[i].an_name != NULL; ++i) {
		attr_names[i].an_rest = 0;
		attr_names[i].an_value = NULL;
	}

	for (; pbs; pbs = pbs->next) {
		if (pbs->attribs == NULL)
			continue; /* move out because it was a single host */
		curhn = get_resource_value(ATTR_rescavail, "host", pbs->attribs);
		if (curhn == NULL)
			continue;
		if (strcasecmp(hostn, curhn) != 0)
			continue;
		for (cupatl = pbs->attribs; cupatl; cupatl = cupatl->next) {
			for (i = 0; attr_names[i].an_name != NULL; ++i) {
				int rest, type;
				if (strcmp(attr_names[i].an_name, cupatl->name) != 0)
					continue;
				type = attr_names[i].an_type;
				rest = attr_names[i].an_rest;
				attr_names[i].an_rest = 1;
				if (rest == 0 && (type & SKIP_FIRST))
					break;
				if (rest && (type & SKIP_REST))
					break;
				if (!(type & UNIQUE) && attr_names[i].an_value == NULL) {
					/* rule 2. - replace null with this string */
					attr_names[i].an_value = strdup(cupatl->value);
					break;
				}
				if (type & UNIQUE) {
					if (attr_names[i].an_value == NULL) {
						if (rest == 0 || (type & SKIP_FIRST)) {
							attr_names[i].an_value = strdup(cupatl->value);
							break;
						}
						attr_names[i].an_value = strdup("<null>");
					}
					/* rule 3.5 suppress duplicates */
					len = strlen(cupatl->value);
					for (sp = attr_names[i].an_value; sp && *sp; ++sp) {
						sp = strstr(sp, cupatl->value);
						if (!sp)
							break;
						if (sp != attr_names[i].an_value && sp[-1] != ' ') {
							continue;
						}
						if (sp[len] == ',' || sp[len] == '\0')
							break;
					}
					if (sp && *sp)
						break;
					/*
					 * Fall through to CATENATE
					 */
					type |= CATENATE;
				}
				if (type & CATENATE) {
					/* rule 3. - concatenate values */
					len = strlen(attr_names[i].an_value) +
					      strlen(cupatl->value) + 3;
					/* 3 for comma, space, and null char */
					dup = malloc(len);
					if (dup) {
						strcpy(dup, attr_names[i].an_value);
						strcat(dup, ", ");
						strcat(dup, cupatl->value);
						free(attr_names[i].an_value);
						attr_names[i].an_value = dup;
					}
					break;
				}
				if ((strcmp(attr_names[i].an_value, various) != 0) && (strcmp(attr_names[i].an_value, cupatl->value) != 0)) {
					/* rule 4. - differing values = "<various>" */
					free(attr_names[i].an_value);
					attr_names[i].an_value = strdup(various);
				}
				break;
			}
		}
	}
#else
static struct attr_names {
	char *an_name;
	char *an_value;
} attr_names[] = {
	{ATTR_NODE_Mom, NULL},
	{ATTR_NODE_Port, NULL},
	{ATTR_version, NULL},
	{ATTR_NODE_ntype, NULL},
	{ATTR_NODE_state, NULL},
	{ATTR_NODE_pcpus, NULL},
	{ATTR_p, NULL},
	{ATTR_NODE_jobs, NULL},
	{ATTR_maxrun, NULL},
	{ATTR_maxuserrun, NULL},
	{ATTR_maxgrprun, NULL},
	{ATTR_NODE_No_Tasks, NULL},
	{ATTR_PNames, NULL},
	{ATTR_NODE_resvs, NULL},
	{ATTR_queue, NULL},
	{ATTR_comment, NULL},
	{ATTR_NODE_resv_enable, NULL},
	{ATTR_NODE_NoMultiNode, NULL},
	{ATTR_NODE_Sharing, NULL},
	{ATTR_NODE_ProvisionEnable, NULL},
	{ATTR_NODE_current_aoe, NULL},
	{ATTR_NODE_License, NULL},
	{ATTR_NODE_LicenseInfo, NULL},
	{ATTR_NODE_TopologyInfo, NULL},
	{ATTR_NODE_VnodePool, NULL},
	{ATTR_NODE_power_provisioning, NULL},
	{ATTR_NODE_current_eoe, NULL},
	{ATTR_NODE_poweroff_eligible, NULL},
	{ATTR_NODE_last_state_change_time, NULL},
	{ATTR_NODE_last_used_time, NULL},
	{NULL, NULL}};

/**
 * @brief
 * 	build_collective - for each vnode in the original batch_status, pbs,
 *	apply the following rules to build "host" attributes in newbsr entry
 *	"the array" refers to the array attr_names[].an_value --
 *	1. if "resources_assigned" or "resources_available", skip for now
 *	2. else if that attribute in the array has a null value, dup the value
 *	3. else if "jobs" attribute, append string to that in the array
 *	4. else if pbs attribute and array value are different, set the array
 *		to "<various>" if not already so set
 *	5. Then add in the resources_available/assigned from the consum struct
 */

static void
build_collective(struct batch_status *pbs,
		 struct batch_status *newbsr,
		 const char *hostn,
		 struct consumable *consum,
		 int consumable_size,
		 char *various)
{
	char convtbuf[256];
	struct attrl *cupatl;
	struct attrl *hdpatl;
	struct attrl *nwpatl;
	char *curhn;
	char *dup;
	int i;
	size_t len;

	for (i = 0; attr_names[i].an_name != NULL; ++i)
		attr_names[i].an_value = NULL;

	for (; pbs; pbs = pbs->next) {
		if (pbs->attribs == NULL)
			continue; /* move out because it was a single host */
		curhn = get_resource_value(ATTR_rescavail, "host", pbs->attribs);
		if (curhn == NULL)
			continue;
		if (strcasecmp(hostn, curhn) == 0) {
			for (cupatl = pbs->attribs; cupatl; cupatl = cupatl->next) {
				if ((strcmp(cupatl->name, ATTR_rescavail) == 0) ||
				    (strcmp(cupatl->name, ATTR_rescassn) == 0))
					continue; /* rule 1. */

				for (i = 0; attr_names[i].an_name != NULL; ++i) {
					if (strcmp(attr_names[i].an_name, cupatl->name) == 0) {

						if (attr_names[i].an_value == NULL) {
							/* rule 2. - replace null with this string */
							if ((attr_names[i].an_value = strdup(cupatl->value)) == NULL) {
								pbs_errno = PBSE_SYSTEM;
								return;
							}
						} else if (strcmp(cupatl->name, ATTR_NODE_jobs) == 0) {
							/* rule 3. - concatenate jobs */
							len = strlen(attr_names[i].an_value) +
							      strlen(cupatl->value) + 3;
							/* 3 for comma, space, and null char */
							dup = malloc(len);
							if (dup) {
								strcpy(dup, attr_names[i].an_value);
								strcat(dup, ", ");
								strcat(dup, cupatl->value);
								free(attr_names[i].an_value);
							} else {
								pbs_errno = PBSE_SYSTEM;
								return;
							}
							attr_names[i].an_value = dup;
						} else if ((strcmp(attr_names[i].an_value, various) != 0) && (strcmp(attr_names[i].an_value, cupatl->value) != 0)) {
							/* rule 4. - differing values = "<various>" */
							free(attr_names[i].an_value);
							if ((attr_names[i].an_value = strdup(various)) == NULL) {
								pbs_errno = PBSE_SYSTEM;
								return;
							}
						}
					}
				}
			}
		}
	}
#endif /* localmod 012 */

	/* Turn the values saved in attr_names into attrl entries */
	/* any attr_names[].an_value with a null value is ignored */

	hdpatl = NULL;

	for (i = 0; attr_names[i].an_name; ++i) {
		if (attr_names[i].an_value) {
			nwpatl = new_attrl();
			if (nwpatl) {
				if (hdpatl == NULL)
					hdpatl = nwpatl; /* head of list */
				else
					cupatl->next = nwpatl;
				if ((nwpatl->name = strdup(attr_names[i].an_name)) == NULL) {
					free_attrl_list(hdpatl);
					pbs_errno = PBSE_SYSTEM;
					return;
				}
				nwpatl->value = attr_names[i].an_value;
				/* note, the above is not dupped */
				attr_names[i].an_value = NULL;
				cupatl = nwpatl;
			} else {
				free_attrl_list(hdpatl);
				pbs_errno = PBSE_SYSTEM;
				return;
			}
		}
	}

	/* then  apply rule 5 - add in resources_available/assigned */
	/* takes two passes, first for resources_available and      */
	/* then for resources_assigned				    */

	for (i = 0; i < consumable_size; ++i) {
		if ((consum + i)->cons_set == 0)
			continue;
		if ((consum + i)->cons_consum) {
			sprintf(convtbuf, "%lld", (consum + i)->cons_avail_sum);
			if ((consum + i)->cons_k)
				strcat(convtbuf, "kb");
			dup = convtbuf;
		} else {
			dup = (consum + i)->cons_avail_str;
		}
		if (dup != NULL) {
			nwpatl = new_attrl();

			if (nwpatl) {
				if (hdpatl == NULL)
					hdpatl = nwpatl;
				else
					cupatl->next = nwpatl;
				nwpatl->next = NULL;
				if ((nwpatl->name = strdup(ATTR_rescavail)) == NULL) {
					free_attrl_list(hdpatl);
					pbs_errno = PBSE_SYSTEM;
					return;
				}
				if ((nwpatl->resource = strdup((consum + i)->cons_resource)) == NULL) {
					free_attrl_list(hdpatl);
					pbs_errno = PBSE_SYSTEM;
					return;
				}
				if ((nwpatl->value = strdup(dup)) == NULL) {
					free_attrl_list(hdpatl);
					pbs_errno = PBSE_SYSTEM;
					return;
				}
				cupatl = nwpatl;
			} else {
				free_attrl_list(hdpatl);
				pbs_errno = PBSE_SYSTEM;
				return;
			}
		}
	}

	/* now do the resources_assigned */
	for (i = 0; i < consumable_size; ++i) {
		if ((consum + i)->cons_set == 0)
			continue;
		if ((consum + i)->cons_consum) {
			sprintf(convtbuf, "%lld", (consum + i)->cons_assn_sum);
			if ((consum + i)->cons_k)
				strcat(convtbuf, "kb");
			nwpatl = new_attrl();
			if (nwpatl) {
				if (hdpatl == NULL)
					hdpatl = nwpatl;
				else
					cupatl->next = nwpatl;
				if ((nwpatl->name = strdup(ATTR_rescassn)) == NULL) {
					free_attrl_list(hdpatl);
					pbs_errno = PBSE_SYSTEM;
					return;
				}
				if ((nwpatl->resource = strdup((consum + i)->cons_resource)) == NULL) {
					free_attrl_list(hdpatl);
					pbs_errno = PBSE_SYSTEM;
					return;
				}
				if ((nwpatl->value = strdup(convtbuf)) == NULL) {
					free_attrl_list(hdpatl);
					pbs_errno = PBSE_SYSTEM;
					return;
				}
				cupatl = nwpatl;
			} else {
				free_attrl_list(hdpatl);
				pbs_errno = PBSE_SYSTEM;
				return;
			}
		}
	}

	/* NOTE: DO NOT free the attr_names[].an_value strings   */
	/* They are in use in the attrls sent back to the caller */

	newbsr->attribs = hdpatl;
}

/**
 * @brief
 * 	build_return_status_list - build a new batch_status list for the reply
 *	or append to the existing list which is passed in.
 */
struct batch_status *
build_return_status(struct batch_status *bstatus,
		    const char *hname,
		    struct batch_status *curlist,
		    struct host_list *phost_list,
		    int host_list_size,
		    struct consumable *consum,
		    int consumable_size,
		    char *various)
{
	struct batch_status *cpbs;
	struct batch_status *npbs;
	int i;

	npbs = malloc(sizeof(struct batch_status));
	if (npbs == NULL) {
		pbs_errno = PBSE_SYSTEM;
		return NULL;
	}

	npbs->next = NULL;
	npbs->text = NULL;

	/* is the host in question a single or multi-vnode host */

	for (i = 0; i < host_list_size; ++i) {
		if (strcasecmp(hname, (phost_list + i)->hl_name) == 0) {
			if ((phost_list + i)->hl_node != NULL) {

				/* single vnode host - use the real one */

				/* prevent double free: copy name, move attribs */
				if ((npbs->name = strdup((phost_list + i)->hl_node->name)) == NULL) {
					free(npbs);
					pbs_errno = PBSE_SYSTEM;
					return NULL;
				}
				npbs->attribs = (phost_list + i)->hl_node->attribs;
				(phost_list + i)->hl_node->attribs = NULL;
				if ((phost_list + i)->hl_node->text)
					if ((npbs->text = strdup((phost_list + i)->hl_node->text)) == NULL) {
						free(npbs->name);
						free(npbs);
						pbs_errno = PBSE_SYSTEM;
						return NULL;
					}

			} else {

				/* multi-vnoded host, build attrls from collection */
				/* of the attrls of all the vnode's on the host    */

				if ((npbs->name = strdup(hname)) == NULL) {
					free(npbs);
					pbs_errno = PBSE_SYSTEM;
					return NULL;
				}
				npbs->attribs = NULL;

				sum_resources(bstatus, npbs, hname,
					      consum, consumable_size, various);
				build_collective(bstatus, npbs, hname,
						 consum, consumable_size, various);
			}

			/* append new to end of the current list */
			if (curlist == NULL) {
				curlist = npbs;
			} else {
				/* curlist must be unchanged here */
				cpbs = curlist;
				while (cpbs->next)
					cpbs = cpbs->next;
				cpbs->next = npbs;
			}
			break;
		}
	}
	if (i == host_list_size) {
		/* did not find a host of the given name in the table */
		free(npbs); /* no leaking */
		pbs_errno = PBSE_UNKNODE;
	}

	return curlist;
}

/**
 * @brief
 * 	pbs_stathost - return status on a single named host or all hosts known
 *	A host is defined by the value of resources_available.host
 *
 *	Function does a pbs_statvnode() to collect information on all vnodes
 *	and then aggregates the attributes from the vndoes that share the same
 *	host value.
 *
 *	If resources in resources_assigned/resources_available are consumable,
 *	so defined by being in resources_assigned, then the values for the same
 *	resource on the collection of vnodes are summed.
 *
 *	Otherwise, if the attribute or resource values are identical across the
 *	set of vnodes, that value is reported.  Else, the string "<various>"
 *	is reported, meaing the vnodes have different values.
 *
 *	This function, like most in PBS, is NOT thread safe.
 */

struct batch_status *
__pbs_stathost(int con, const char *hid, struct attrl *attrib, const char *extend)
{
	struct batch_status *breturn; /* the list returned to the caller */
	struct batch_status *bstatus; /* used internally		   */
	int i;
	/* variables used across many function, these are what make the function */
	char *various = "<various>";
	struct host_list *phost_list = NULL;
	struct consumable *consum = NULL;
	int host_list_size = 0;
	int consumable_size = 0;
	struct pbs_client_thread_connect_context *context;

	breturn = NULL;

	/* get status of all vnodes */
	bstatus = pbs_statvnode(con, "", attrib, extend);

	if (bstatus == NULL)
		return NULL;

	build_host_list(bstatus, &phost_list, &host_list_size,
			&consum, &consumable_size);

	if ((hid == NULL) || (*hid == '\0')) {

		/*
		 * No host specified, so for each host found in the host_list
		 * entries, gather info from the vnodes associated with that host
		 */

		for (i = 0; i < host_list_size; ++i) {
			hid = (phost_list + i)->hl_name;
			breturn = build_return_status(bstatus, hid, breturn,
						      phost_list, host_list_size,
						      consum, consumable_size,
						      various);
		}

	} else {

		/*
		 * Specific host names gather info from the vnodes associate with it
		 */

		breturn = build_return_status(bstatus, hid, breturn,
					      phost_list, host_list_size,
					      consum, consumable_size,
					      various);
		if ((breturn == NULL) && (pbs_errno == PBSE_UNKNODE)) {
			/*
			 * store error in TLS if available. Fallback to
			 * connection structure.
			 */
			context = pbs_client_thread_find_connect_context(con);
			if (context != NULL) {
				if (context->th_ch_errtxt != NULL)
					free(context->th_ch_errtxt);
				if ((context->th_ch_errtxt = strdup(pbse_to_txt(pbs_errno))) == NULL) {
					pbs_errno = PBSE_SYSTEM;
					return NULL;
				}
			} else {
				if (set_conn_errtxt(con, pbse_to_txt(pbs_errno)) != 0) {
					pbs_errno = PBSE_SYSTEM;
					return NULL;
				}
			}
		}
	}

	pbs_statfree(bstatus); /* free info returned by pbs_statvnodes() */
	for (i = 0; i < consumable_size; ++i)
		free((consum + i)->cons_resource);
	free(consum);
	consum = NULL;
	consumable_size = 0;
	free(phost_list);
	phost_list = NULL;
	host_list_size = 0;
	return breturn;
}
